/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package edu.isi.pegasus.planner.invocation;

import edu.isi.pegasus.common.util.Currently;
import java.util.*;
import java.net.InetAddress;
import java.io.Writer;
import java.io.IOException;

/**
 * This class is the container for an invocation record. The record
 * itself contains information about the job or jobs that ran, the total
 * usage, and information about central files that were used.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 2587 $
 */
public class InvocationRecord extends Invocation // implements Cloneable
{
  /**
   * The "official" namespace URI of the invocation record schema.
   */
  public static final String SCHEMA_NAMESPACE =
    "http://pegasus.isi.edu/schema/invocation";

  /**
   * The "not-so-official" location URL of the invocation record definition.
   */
  public static final String SCHEMA_LOCATION =
    "http://pegasus.isi.edu/schema/iv-2.1.xsd";



  /**
   * protocol version information.
   */
  private String m_version;

  /**
   * start of gridlaunch timestamp.
   */
  private Date m_start;

  /**
   * total duration of call.
   */
  private double m_duration;

  /**
   * Name of the: Transformation that produced this invocation.
   */
  private String m_transformation;

  /**
   * Name of the Derivation that produced this invocation.
   */
  private String m_derivation;

  /**
   * Records the physical memory on the remote machine, if available.
   */
  private long m_pmem = -1; 

  /**
   * process id of gridlaunch itself.
   */
  private int m_pid;

  /**
   * host address where gridlaunch ran (primary interface).
   */
  private InetAddress m_hostaddr;

  /**
   * Symbolic hostname where gridlaunch ran (primary interface).
   */
  private String m_hostname;

  /**
   * Symbolic name of primary interface we used to determine host-name and -address.
   */
  private String m_interface;

  /**
   * numerical user id of the effective user.
   */
  private int m_uid;

  /**
   * symbolical user name of the effective user.
   */
  private String m_user;

  /**
   * numerical group id of the effective user.
   */
  private int m_gid;

  /**
   * symbolical group name of the effective user.
   */
  private String m_group;

  /**
   * Working directory at startup.
   */
  private WorkingDir m_cwd;

  /**
   * Architectural information.
   */
  private Architecture m_uname;

  /**
   * Total resource consumption by gridlaunch and all siblings.
   */
  private Usage m_usage;

  /**
   * Job records: prejob, main job, postjob
   */
  private List m_job;

  /**
   * Array with stat() and fstat() information about various files.
   */
  private List m_stat;

  /**
   * Resource, site or pool at which the jobs was run.
   */
  private String m_resource;

  /**
   * Workflow label, currently optional?
   */
  private String m_wf_label;

  /**
   * Workflow timestamp to make the label more unique.
   */
  private Date m_wf_stamp;

  /**
   * Environment settings.
   */
  private Environment m_environment;

  /**
   * Currently active umask while kickstart was executing. This
   * is available with new kickstart, older version will have -1
   * at this API point.
   */
  private int m_umask = -1;
  
  /**
   * The Machine object capturing machine information.
   */
  private Machine m_machine;

  /**
   * Accessor
   *
   * @see #setUMask(int)
   */
  public int getUMask()
  { return this.m_umask; }

  /**
   * Accessor.
   *
   * @param umask
   * @see #getUMask()
   */
  public void setUMask( int umask )
  { this.m_umask = umask; }

  /**
   * Default c'tor: Construct a hollow shell and allow further
   * information to be added later.
   */
  public InvocationRecord()
  {
    m_stat = new ArrayList(5);
    m_job = new ArrayList(3);
    m_environment = null;
  }

  /**
   * Accessor
   *
   * @see #setVersion(String)
   */
  public String getVersion()
  { return this.m_version; }

  /**
   * Accessor.
   *
   * @param version
   * @see #getVersion()
   */
  public void setVersion( String version )
  { this.m_version = version; }

  /**
   * Accessor
   *
   * @see #setStart(Date)
   */
  public Date getStart()
  { return this.m_start; }

  /**
   * Accessor.
   *
   * @param start
   * @see #getStart()
   */
  public void setStart( Date start )
  { this.m_start = start; }

  /**
   * Accessor
   *
   * @see #setDuration(double)
   */
  public double getDuration()
  { return this.m_duration; }

  /**
   * Accessor.
   *
   * @param duration
   * @see #getDuration()
   */
  public void setDuration( double duration )
  { this.m_duration = duration; }


  /**
   * Accessor.
   *
   * @param machine
   * @see #getMachine()
   */
  public void setMachine( Machine machine )
  { this.m_machine = machine; }

  /**
   * Accessor.
   *
   * @return  machine
   * @see #setMachine(org.griphyn.vdl.invocation.Machine)
   */
  public Machine getMachine(  )
  { return this.m_machine; }

  
  /**
   * Accessor
   *
   * @see #setTransformation(String)
   */
  public String getTransformation()
  { return this.m_transformation; }

  /**
   * Accessor.
   *
   * @param transformation
   * @see #getTransformation()
   */
  public void setTransformation( String transformation )
  { this.m_transformation = transformation; }

  /**
   * Accessor
   *
   * @see #setDerivation(String)
   */
  public String getDerivation()
  { return this.m_derivation; }

  /**
   * Accessor.
   *
   * @param derivation
   * @see #getDerivation()
   */
  public void setDerivation( String derivation )
  { this.m_derivation = derivation; }

  /**
   * Accessor
   *
   * @see #setPID(int)
   */
  public int getPID()
  { return this.m_pid; }

  /**
   * Accessor.
   *
   * @param pid
   * @see #getPID()
   */
  public void setPID( int pid )
  { this.m_pid = pid; }

  /**
   * Accessor
   *
   * @see #setHostAddress(InetAddress)
   */
  public InetAddress getHostAddress()
  { return this.m_hostaddr; }

  /**
   * Accessor.
   *
   * @param hostaddr
   * @see #getHostAddress()
   */
  public void setHostAddress( InetAddress hostaddr )
  { this.m_hostaddr = hostaddr; }

  /**
   * Accessor
   *
   * @see #setHostname(String)
   */
  public String getHostname()
  { return this.m_hostname; }

  /**
   * Accessor.
   *
   * @param hostname
   * @see #getHostname()
   */
  public void setHostname( String hostname )
  { this.m_hostname = hostname; }

  /**
   * Accessor.
   *
   * @see #setInterface(String)
   */
  public String getInterface()
  { return this.m_interface; }

  /**
   * Accessor.
   *
   * @param p_interface
   * @see #getInterface()
   */
  public void setInterface( String p_interface )
  { this.m_interface = p_interface; }

  /**
   * Accessor
   *
   * @see #setUID(int)
   */
  public int getUID()
  { return this.m_uid; }

  /**
   * Accessor.
   *
   * @param uid
   * @see #getUID()
   */
  public void setUID( int uid )
  { this.m_uid = uid; }

  /**
   * Accessor
   *
   * @see #setUser(String)
   */
  public String getUser()
  { return this.m_user; }

  /**
   * Accessor.
   *
   * @param user
   * @see #getUser()
   */
  public void setUser( String user )
  { this.m_user = user; }

  /**
   * Accessor
   *
   * @see #setGID(int)
   */
  public int getGID()
  { return this.m_gid; }

  /**
   * Accessor.
   *
   * @param gid
   * @see #getGID()
   */
  public void setGID( int gid )
  { this.m_gid = gid; }

  /**
   * Accessor
   *
   * @see #setGroup(String)
   */
  public String getGroup()
  { return this.m_group; }

  /**
   * Accessor.
   *
   * @param group
   * @see #getGroup()
   */
  public void setGroup( String group )
  { this.m_group = group; }

  /**
   * Accessor
   *
   * @see #setUsage(Usage)
   */
  public Usage getUsage()
  { return this.m_usage; }

  /**
   * Accessor.
   *
   * @param usage
   * @see #getUsage()
   */
  public void setUsage( Usage usage )
  { this.m_usage = usage; }

  /**
   * Accessor
   *
   * @see #setArchitecture(Architecture)
   */
  public Architecture getArchitecture()
  { return this.m_uname; }

  /**
   * Accessor.
   *
   * @param uname
   * @see #getArchitecture()
   */
  public void setArchitecture( Architecture uname )
  { this.m_uname = uname; }

  /**
   * Accessor
   *
   * @see #setResource( String )
   */
  public String getResource()
  { return this.m_resource; }

  /**
   * Accessor.
   *
   * @param resource
   * @see #getResource()
   */
  public void setResource( String resource )
  { this.m_resource = resource; }

  /**
   * Accessor
   *
   * @see #setWorkflowLabel( String )
   */
  public String getWorkflowLabel()
  { return this.m_wf_label; }

  /**
   * Accessor.
   *
   * @param label
   * @see #getWorkflowLabel()
   */
  public void setWorkflowLabel( String label )
  { this.m_wf_label = label; }

  /**
   * Accessor
   *
   * @see #setWorkflowTimestamp( Date )
   */
  public Date getWorkflowTimestamp()
  { return this.m_wf_stamp; }

  /**
   * Accessor.
   *
   * @param stamp
   * @see #getResource()
   */
  public void setWorkflowTimestamp( Date stamp )
  { this.m_wf_stamp = stamp; }

  /**
   * Accessor
   *
   * @see #setEnvironment(Environment)
   */
  public Environment getEnvironment()
  { return this.m_environment; }

  /**
   * Accessor.
   *
   * @param environment
   * @see #getEnvironment()
   */
  public void setEnvironment( Environment environment )
  { this.m_environment = environment; }

//   /**
//    * Parses an ISO 8601 timestamp?
//    *
//    * @param stamp
//    * @see #getResource()
//    */
//   public void setWorkflowTimestamp( String stamp )
//   { this.m_wf_stamp = stamp; }




  /**
   * Accessor: Appends a job to the list of jobs.
   *
   * @param job is the job to append to the list.
   */
  public void addJob( Job job )
  { this.m_job.add(job); }

  /**
   * Accessor: Inserts a Job into a specific position of the job list.
   *
   * @param index is the position to insert the item into
   * @param job is the job to insert into the list.
   */
  public void addJob( int index, Job job )
  { this.m_job.add( index, job ); }


  /**
   * Accessor: Obtains a job at a certain position in the job list.
   *
   * @param index is the position in the list to obtain a job from
   * @return the job at that position.
   * @throws IndexOutOfBoundsException if the index points to an element
   *         in the list that does not contain any elments.
   */
  public Job getJob( int index )
    throws IndexOutOfBoundsException
  {
    //-- check bound for index
    if ((index < 0) || (index >= this.m_job.size()))
      throw new IndexOutOfBoundsException();

    return (Job) this.m_job.get(index);
  }

  /**
   * Accessor: Obtains the size of the job list.
   *
   * @return number of elements that an external array needs to be sized to.
   */
  public int getJobCount()
  {
    return this.m_job.size();
  }

  /**
   * Accessor: Gets an array of all values that constitute the current
   * content. This list is read-only.
   *
   * @return a list of jobs.
   */
  public java.util.List getJobList()
  {
    return Collections.unmodifiableList(this.m_job);
  }

  /**
   * Accessor: Enumerates the internal values that constitute the content
   * of the job list.
   *
   * @return an iterator to walk the list with.
   */
  public Iterator iterateJob()
  {
    return this.m_job.iterator();
  }

  /**
   * Accessor: Enumerates the internal values that constitute the content
   * of the job list.
   *
   * @return a list iterator to walk the list with.
   */
  public ListIterator listIterateJob()
  {
    return this.m_job.listIterator();
  }

  /**
   * Accessor: Removes all values from the job list.
   */
  public void removeAllJob()
  {
    this.m_job.clear();
  }


  /**
   * Accessor: Removes a specific job from the job list.
   * @param index is the position at which an element is to be removed.
   * @return the job that was removed.
   */
  public Job removeJob( int index )
  {
    return (Job) this.m_job.remove(index);
  }

  /**
   * Accessor: Overwrites a job at a certain position.
   *
   * @param index position to overwrite an elment in.
   * @param job is the Job to replace with.
   * @throws IndexOutOfBoundsException if the position pointed to is invalid.
   */
  public void setJob( int index, Job job )
    throws IndexOutOfBoundsException
  {
    //-- check bounds for index
    if ((index < 0) || (index >= this.m_job.size())) {
      throw new IndexOutOfBoundsException();
    }
    this.m_job.set(index, job);
  }

  /**
   * Accessor: Overwrites internal list with an external list
   * representing jobs.
   *
   * @param jobs is the external list of job to overwrite with.
   */
  public void setJob( Collection jobs )
  {
    this.m_job.clear();
    this.m_job.addAll(jobs);
  }




  /**
   * Accessor: Appends a stat to the list of stats.
   *
   * @param stat is the stat to append to the list.
   */
  public void addStatCall( StatCall stat )
  { this.m_stat.add(stat); }

  /**
   * Accessor: Inserts a StatCall into a specific position of the stat list.
   *
   * @param index is the position to insert the item into
   * @param stat is the stat to insert into the list.
   */
  public void addStatCall( int index, StatCall stat )
  { this.m_stat.add( index, stat ); }


  /**
   * Accessor: Obtains a stat at a certain position in the stat list.
   *
   * @param index is the position in the list to obtain a stat from
   * @return the stat at that position.
   * @throws IndexOutOfBoundsException if the index points to an element
   *         in the list that does not contain any elments.
   */
  public StatCall getStatCall( int index )
    throws IndexOutOfBoundsException
  {
    //-- check bound for index
    if ((index < 0) || (index >= this.m_stat.size()))
      throw new IndexOutOfBoundsException();

    return (StatCall) this.m_stat.get(index);
  }

  /**
   * Accessor: Obtains the size of the stat list.
   *
   * @return number of elements that an external array needs to be sized to.
   */
  public int getStatCount()
  {
    return this.m_stat.size();
  }

  /**
   * Accessor: Gets an array of all values that constitute the current
   * content. This list is read-only.
   *
   * @return a list of stats.
   */
  public java.util.List getStatList()
  {
    return Collections.unmodifiableList(this.m_stat);
  }

  /**
   * Accessor: Enumerates the internal values that constitute the content
   * of the stat list.
   *
   * @return an iterator to walk the list with.
   */
  public Iterator iterateStatCall()
  {
    return this.m_stat.iterator();
  }

  /**
   * Accessor: Enumerates the internal values that constitute the content
   * of the stat list.
   *
   * @return a list iterator to walk the list with.
   */
  public ListIterator listIterateStatCall()
  {
    return this.m_stat.listIterator();
  }

  /**
   * Accessor: Removes all values from the stat list.
   */
  public void removeAllStatCall()
  {
    this.m_stat.clear();
  }


  /**
   * Accessor: Removes a specific stat from the stat list.
   * @param index is the position at which an element is to be removed.
   * @return the stat that was removed.
   */
  public StatCall removeStatCall( int index )
  {
    return (StatCall) this.m_stat.remove(index);
  }

  /**
   * Accessor: Overwrites a stat at a certain position.
   *
   * @param index position to overwrite an elment in.
   * @param stat is the StatCall to replace with.
   * @throws IndexOutOfBoundsException if the position pointed to is invalid.
   */
  public void setStatCall( int index, StatCall stat )
    throws IndexOutOfBoundsException
  {
    //-- check bounds for index
    if ((index < 0) || (index >= this.m_stat.size())) {
      throw new IndexOutOfBoundsException();
    }
    this.m_stat.set(index, stat);
  }

  /**
   * Accessor: Overwrites internal list with an external list
   * representing stats.
   *
   * @param stats is the external list of stat to overwrite with.
   */
  public void setStatCall( Collection stats )
  {
    this.m_stat.clear();
    this.m_stat.addAll(stats);
  }

  /**
   * Accessor
   *
   * @see #setWorkingDirectory(WorkingDir)
   * @see #setWorkingDirectory(String)
   */
  public WorkingDir getWorkingDirectory()
  { return this.m_cwd; }

  /**
   * Accessor.
   *
   * @param cwd
   * @see #getWorkingDirectory()
   * @see #setWorkingDirectory(WorkingDir)
   */
  public void setWorkingDirectory( String cwd )
  { this.m_cwd = new WorkingDir(cwd); }

  /**
   * Accessor.
   *
   * @param cwd
   * @see #getWorkingDirectory()
   * @see #setWorkingDirectory(String)
   */
  public void setWorkingDirectory( WorkingDir cwd )
  { this.m_cwd = cwd; }

  /**
   * Accessor.
   *
   * @return the recorded physical memory in byte, or -1 if not available. 
   * @see #setPhysicalMemory( long )
   */ 
  public long getPhysicalMemory()
  { return this.m_pmem; }

  /**
   * Accessor.
   *
   * @param pmem
   * @see #getPhysicalMemory()
   */
  public void setPhysicalMemory( long pmem ) 
  { this.m_pmem = pmem; }

  /**
   * Converts the active state into something meant for human consumption.
   * The method will be called when recursively traversing the instance
   * tree.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   */
  public void toString( Writer stream )
    throws IOException
  {
    throw new IOException( "method not implemented, please contact vds-support@griphyn.org" );
  }

  /**
   * Writes the header of the XML output. The output contains the special
   * strings to start an XML document, some comments, and the root element.
   * The latter points to the XML schema via XML Instances.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @param indent is a <code>String</code> of spaces used for pretty
   * printing. The initial amount of spaces should be an empty string.
   * The parameter is used internally for the recursive traversal.
   * @param namespace is the XML schema namespace prefix. If neither
   * empty nor null, each element will be prefixed with this prefix,
   * and the root element will map the XML namespace.
   * @exception IOException if something fishy happens to the stream.
   */
  public void writeXMLHeader( Writer stream, String indent, String namespace )
    throws IOException
  {
    String newline = System.getProperty( "line.separator", "\r\n" );

    // intro
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" );
    stream.write( newline );

    // when was this document generated
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "<!-- generated: " );
    stream.write( Currently.iso8601(false) );
    stream.write( " -->" );
    stream.write( newline );

    // who generated this document
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "<!-- generated by: " );
    stream.write( System.getProperties().getProperty("user.name", "unknown") );
    stream.write( " [" );
    stream.write( System.getProperties().getProperty("user.region","??") );
    stream.write( "] -->" );
    stream.write( newline );

    // root element with elementary attributes
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( '<' );
    if ( namespace != null && namespace.length() > 0 ) {
      stream.write( namespace );
      stream.write( ':' );
    }
    stream.write( "invocation xmlns" );
    if ( namespace != null && namespace.length() > 0 ) {
      stream.write( ':' );
      stream.write( namespace );
    }
    stream.write( "=\"");
    stream.write( SCHEMA_NAMESPACE );
    stream.write( "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"" );
    stream.write( SCHEMA_NAMESPACE );
    stream.write( ' ' );
    stream.write( SCHEMA_LOCATION );
    stream.write( "\"");

    writeAttribute( stream, " version=\"", this.m_version );
    writeAttribute( stream, " start=\"",
		    Currently.iso8601(false,true,true,this.m_start) );
    writeAttribute( stream, " duration=\"", Double.toString(this.m_duration) );
    if ( this.m_transformation != null && this.m_transformation.length() > 0 )
      writeAttribute( stream, " transformation=\"", this.m_transformation );
    if ( this.m_derivation != null && this.m_derivation.length() > 0 )
      writeAttribute( stream, " derivation=\"", this.m_derivation );
    if ( this.m_pmem != -1 ) 
      writeAttribute( stream, " ram=\"", Long.toString(this.m_pmem) );
    writeAttribute( stream, " pid=\"", Integer.toString( this.m_pid ) );
    if ( this.m_resource != null && this.m_resource.length() > 0 )
      writeAttribute( stream, " resource=\"", this.m_resource );
    if ( this.m_wf_label != null && this.m_wf_label.length() > 0 )
      writeAttribute( stream, " wf-label=\"", this.m_wf_label );
    if ( this.m_wf_stamp != null )
      writeAttribute( stream, " wf-stamp=\"",
		      Currently.iso8601(false,true,true,this.m_wf_stamp) );
    writeAttribute( stream, " hostaddr=\"", this.m_hostaddr.getHostAddress() );
    if ( this.m_hostname != null && this.m_hostname.length() > 0 )
      writeAttribute( stream, " hostname=\"", this.m_hostname );
    writeAttribute( stream, " uid=\"", Integer.toString( this.m_uid ) );
    if ( this.m_user != null && this.m_user.length() > 0 )
      writeAttribute( stream, " user=\"", this.m_user );
    writeAttribute( stream, " gid=\"", Integer.toString( this.m_gid ) );
    if ( this.m_group != null && this.m_group.length() > 0 )
      writeAttribute( stream, " group=\"", this.m_group );
    
    stream.write( '>' );
    if ( indent != null ) stream.write( newline );
  }

  /**
   * Dump the state of the current element as XML output. This function
   * traverses all sibling classes as necessary, and converts the data
   * into pretty-printed XML output. The stream interface should be able
   * to handle large output efficiently.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @param indent is a <code>String</code> of spaces used for pretty
   * printing. The initial amount of spaces should be an empty string.
   * The parameter is used internally for the recursive traversal.
   * @param namespace is the XML schema namespace prefix. If neither
   * empty nor null, each element will be prefixed with this prefix,
   * and the root element will map the XML namespace.
   * @exception IOException if something fishy happens to the stream.
   */
  public void toXML( Writer stream, String indent, String namespace )
    throws IOException
  {
    // write prefix
    writeXMLHeader( stream, indent, namespace );

    // part 1: jobs
    String newindent = indent==null ? null : indent+"  ";
    for ( Iterator i=this.m_job.iterator(); i.hasNext(); ) {
      ((Job) i.next()).toXML( stream, newindent, namespace );
    }

    // part 2: cwd and total usage
    m_cwd.toXML( stream, newindent, namespace );
    m_usage.toXML( stream, newindent, namespace );
    m_uname.toXML( stream, newindent, namespace );
    //machine if not null
    if( m_machine != null ){
        m_machine.toXML( stream, indent, namespace );
    }
    
    // part 3: statcall records
    for ( Iterator i=this.m_stat.iterator(); i.hasNext(); ) {
      ((StatCall) i.next()).toXML( stream, newindent, namespace );
    }

    // part 4: environment and resourcs
    if ( m_environment != null )
      m_environment.toXML( stream, newindent, namespace );

    // close tag
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "</" );
    if ( namespace != null && namespace.length() > 0 ) {
      stream.write( namespace );
      stream.write( ':' );
    }
    stream.write( "invocation>" );
    stream.write( System.getProperty( "line.separator", "\r\n" ) );
    stream.flush(); // this is the only time we flush
  }
}
